MagnaMedia · AMIGA-Magazin · Intuition-Programmierung unter p.OS, Folge 3

Aktuelles Heft 9/97

Intuitive Benutzeroberflächen


Im dritten und letzten Kursteil der p.OS-Programmierung werden wir das Kapitel Intuition unter p.OS mit den Menüs und dem EasyRequester abschließen. Zum besseren Verständnis werden wir natürlich unser Beispielprogramm aus den vorangegangenen Teilen wieder erweitern. Es wird dann kein Problem mehr für Sie sein, Programme unter p.OS zu entwickeln.

# von Michael Christoph

Da Ihnen p.OS die meisten Aufgaben beim Erstellen der Oberfläche abnimmt (Fontsensitiv, Menülayout, Gadgetpositionierung, Windowresizing, ...), können Sie sich ganz der Implementierung der gewünschten Aufgaben widmen. Unser Beispielprogramm wird Sie dabei zusätzlich unterstützen, indem Sie benötigte Programmteile (Gadgets/ Menüs/Messageloop/...) einfach per Cut-Copy-Paste übernehmen.

Beachten Sie dabei aber stets, daß für alle verwendeten Funktionen die entsprechenden Betriebsystem-Libraries und -Devices geöffnet sein müssen. Diese sind am Programmende dann wieder alle zu schließen. Beachten Sie auch, daß im Fehlerfall ein kontrollierter Programmabbruch stattfinden muß, bei dem alle reservieren Resourcen wieder freizugeben sind. Im Fehlerfall muß auch immer eine Mitteilung an den Benutzer erfolgen (z.B. per pOS_ PrintDosErr() oder pOS_ SPrintf()). Auch bei einem Workbenchstart unter p.OS dürfen Sie die pDOS-Ausgabefunktionen benutzen.

Im Bedarfsfall wird ein Console-Fenster zur Ausgabe geöffnet. Bei Oberflächen-Programmen empfiehlt es sich aber, den Fehler über Requester (z.B. pOS_EasyRequest Args()) anzuzeigen. Die Fehlertexte sollten dabei für jederman verständlich und ausführlich sein. Statt »Lock failed« verwenden Sie besser »Datei %s ist nicht zu finden.«.
Dabei sollte auch der (vollständige) Dateiname angegeben werden. Falls Sie zusätzliche Informationen bei einer Fehlermeldung wünschen, können Sie z.B. einen programmspezifischen Fehlercode in Klammern mit ausgeben lassen. Beachten Sie, daß im Fehlerfall unerfahrene Benutzer meist nur den halben Fehlertext, ohne weitere Angaben, wiedergeben können. Erfahrene User werden bei ausführlichen Fehlertexten den Fehler meist selbständig beheben.

Auf die ANSI-Funktionen exit()/atexit()/abort() muß im Fehlerfall unbedingt verzichtet werden, da diese intern auf Amiga-Funktionen zurückgreifen, was unweigerlich zu einem Programmfehler führt. In Zukunft wird Ihnen dafür die Funktion pOS_ExitProcess() zur Verfügung stehen. Es ist aber immer besser, wenn die Programme (und auch die Funktionen) nur einen Ausstiegspunkt (eine Return-Anweisung) enthalten und dadurch auf harten Programmausstieg verzichten. Jedes Programm (zumindest bei strukturierten Programmiersprachen wie C oder C++) läßt sich so entwickeln, daß diese Bedingung zutrifft. C++ (Version 3) bietet hierzu auch das Exception-Handling an.
Damit ist es sehr einfach, aus jeder tiefen Unterfunktion wieder zu einem kontrollierbaren Punkt zurückzukehren. Nach Möglichkeit sollte das Programm nach der Fehlermeldung (evtl. auf Benutzerwunsch) weiterlaufen.

Programmprojekt unter p.OS: Unser selbstgestaltetes Fenster hat noch ein Menü bekommen
Sie können z.B. in der main()-Routine nur die Argumente parsen, die Libraries öffnen und alles weitere von einer Unterfunktion erledigen lassen. Diese kehrt erst am Programmende oder im Fehlerfall (mit dem Shell-Fail-Code) zurück. Danach werden die Libraries geschlossen und das Programm über die main()-Routine beendet. Dabei können Sie der aufrufenden Shell noch einen Returncode zurückgeben.Das erreichen Sie über die Funktion pOS_SetShellFail(). Vordefinierte Fail-Werte in "p:pDOS/DosErrors.h" sind:

 
- DOSFAIL_OK     (0) : Programm wurde fehlerlos abgearbeitet
- DOSFAIL_WARN   (5) : Warnung, z.B. bei fehlerhaften Parametern
- DOSFAIL_ABORT  (8) : Benutzerabbruch durch CTRL-C
- DOSFAIL_ERROR (10) : einfacher Fehler
- DOSFAIL_FAIL  (20) : großer Fehler, z.B. Speichermangel

Bei Bedarf können Sie auch programmspezifische Fehlercodes setzen, die ab »21« beginnen dürfen. Normalerweise ist das aber nicht notwendig. Die Shell kann in Scripts auch auf den Returncode reagieren. Die Funktion »IF« ermöglicht eine Unterscheidung zwischen Warn/Error/Fail (z.B. nur nach korrekter Diskettenformatierung werden Daten kopiert).
Nach diesem Ausflug ins p.OS-StyleGuide wenden wir uns nun den p.OS-Menüs zu. Der erste sichtbare Unterschied zum AmigaOS: die Menüs werden nicht mehr in der Bildschirmtitelzeile angezeigt, sondern auf der Titelzeile des zugehörigen Fensters. Die Anordnung wird später evtl. frei wählbar sein, was aber auf die Programmierung der Menüs keinen Einfluß hat.

Der zweite Punkt, der nur wenigen Anwendern auffallen wird, ist, daß das Window während der Menüauswahl nicht mehr gesperrt ist. Dadurch entfällt eine mögliche Deadlock-Situation, wie sie beim Amiga auftreten könnte.
Die Menüstruktur wird, ähnlich den Gadgets, nur beschrieben. Die Menüanordnung und Positionierung übernimmt das OS wieder vollständig für Sie. Die Menüeinträge sind wiederum Objekte, wodurch auch Grafiken und Animationen als Einträge kein Problem sind. Die Menüstruktur finden Sie in »p:pIntui/Menu.h«. Interessant sind hierbei mt_Type für Title/Item/Sub, mt_Lable für den Texteintrag und mt_CommKey für einen Shortkey. Seltener benötigt werden mt_Flags (z.B. zum Abhaken) und mt_MutualExclude (für Ausschlußpunkte). Die angesprochenen Grafiken und Animationen bekommen Sie ins Menü über eine IntuiObj-Beschreibung in mt_Tags. Das Ende der Menübeschreibung wird per mt_Type MENUTAGTYP_End abgeschlossen.

 
Kursübersicht
Dieser dreiteilige Programmierkurs soll die Konzepte zur Entwicklung von GUIs (Graphical User Interfaces = Grafische Benutzeroberflächen) unter pOS vorstellen. Begleitend wird stufenweise ein einfaches Programm entwickelt, um auch die praktische Seite nicht zu vernachlässigen.

Folge 1: Beschreibung des pIntui-Konzepts, Beispielprogramm mit Fenster öffnen und leerem Messageloop Am Ende dieses ersten Teils haben Sie ein Programmgerüst, das die übergebenen Argumente parsen kann, ein Fenster öffnet und auf das Anklicken des Schließ-Symbols wartet. Dieses Programmgerüst können Sie als Ausgangsbasis für jedes neue Programm verwenden.

Folge 2: Beschreibung von Gadget/IObject-Konzept von pOS und BubbleHelp, Beispielprogramm erweitert um Gadgets und Messageloop. Im zweiten Kursteil wird das Fenster lebendig, d.h. wir werden es mit vielen verschiedenen Gadgettypen füllen. Dazu wird das Gadgetkonzept von pOS näher beleuchtet, die einzelnen Gadget-Tags vorgestellt und der Messageloop um die Gadgetinterkommunikation erweitert.

Folge 3: Beschreibung von Menüaufbau/auswertung und Requestern Beispielprogramm erweitert um Menüs, EasyRequester und Messageloop Im dritten und letzten Kursteil erhält das Fenster noch ein Menü spendiert und natürlich wird der Messageloop nochmals um die Menüverwaltung erweitert. Zusätzlich wird ein Easy-Requester für die Programminformation ergänzt.
Beachten Sie, daß unter p.OS zwischen Item- und Sub-Trennlinien unterschieden wird. Die Menu-Flags setzen sich wie folgt zusammen: Bei normalen Auswahlpunkten wird hier 0 eingetragen. Soll der Menüpunkt aktivierbar sein, läßt sich der per MENUITF_Hook bestimmen. Bei Auswahl wird der Punkt mit einem vorangestellten Haken ausgestattet.

Mit MENUITF_Toggle wird bei jeder Auswahl der Hakenzustand gewechselt. Ob ein Punkt aktiviert ist, können Sie beim Start über MENUTIF_ IsChecked festlegen. Die Wählbarkeit definiert man über MENUITF_Disabled (gesetzt = nicht wählbar).
Einer Erklärung bedarf noch der Eintrag für mt_MutualExclude. Die Funktionsweise ist ähnlich den MX-Gadgets. Bei der Auswahl eines Menüpunktes bestimmt die Maske in mt_MutualExclude, welche anderen Menüpunkte auszuschalten sind.
Es lassen sich nur die Einträge der aktuellen Menüebene beeinflussen. Dabei werden maximal die ersten 32 Einträge beeinflußt (32 Bits im ULONG). Besser verdeutlichen läßt sich das an einem Beispiel, etwa die Style-Auswahl für eine Schrift. Hierbei kann entweder Plain oder eine Kombination aus Bold, Italic und Underline auftreten. Dabei ist bei der Auswahl von Plain immer dieser Punkt abzuhaken, alle anderen muß man deaktivieren. Die anderen drei Punkte können einzeln ein- und ausgeschaltet werden. Aus dieser Anforderung ergibt sich folgende Menübeschreibung:

 
mt_Lable         = "Plain"
mt_Flags         = MENUITF_Hook | MENUITF_IsChecked
mt_MutualExclude = 0x0E   /* %00001110 => B/I/U ausschalten */
mt_Lable         = "Bold"
mt_Flags         = MENUITF_Hook | MENUITF_Toggle
mt_MutualExclude = 0x01   /* %00000001 => Plain aus */
mt_Lable         = "Italic"
mt_Flags         = MENUITF_Hook | MENUITF_Toggle
mt_MutualExclude = 0x01   /* %00000001 => Plain aus */
mt_Lable         = "Underline"
mt_Flags         = MENUITF_Hook | MENUITF_Toggle
mt_MutualExclude = 0x01   /* %00000001 => Plain aus */

Der Eintrag Plain ist beim Start abgehakt und besitzt kein Toggle-Flag, um diesen Punkt bei mehrfacher Auswahl von Plain nicht zu deaktiveren, wodurch kein Punkt mehr aktiv wäre. Es gibt aber trotzdem eine Situation, die das OS nicht abfangen kann. Wählen Sie z.B. Bold, wird der Punkt abgehakt und Plain deaktiviert. Bei der zweiten Auswahl von Bold wird der Punkt wieder deaktivert und keiner der vier Punkte ist mehr gesetzt. Diesen Fall müssen Sie zwangsläufig selbst im Messageloop abfangen (vereinfachte Darstellung):

 
if(
pOS_GetWindowMenuChecker(0)==FALSE     &&
pOS_GetWindowMenuChecker(1)==FALSE     &&
pOS_GetWindowMenuChecker(2)==FALSE     &&
pOS_GetWindowMenuChecker(3)==FALSE)
pOS_SetWindowMenuChecker(0,TRUE);

Einfacher wäre die Aufgabe, wenn ein Punkt immer alle anderen ausschließen würde: In diesem Fall geben Sie für jeden Punkt nur das Hook-Flag an. Dadurch wird bei doppelter Auswahl desselben Punktes kein Toggle durchgeführt und der Punkt bleibt weiterhin angewählt. Die vier MutualExclude-Einträge lauten dann:

 
0x0E   /* %00001110 */
0x0D   /* %00001101 */
0x0B   /* %00001011 */
0x07   /* %00000111 */

Die Maske läßt sich, wie bereits erwähnt, auf die ersten 32 Menüeinträge ausdehnen. Beachten Sie, daß Trennlinien auch einen Eintrag darstellen, der nicht beeinflußbar ist und daher in der Maske durch eine 0 angezeigt werden sollte.
Die MX-Gadgets arbeiten intern nach demselben Schema. Allerdings ist hier die »nur einer aktiv«-Schaltung vorgegeben. Ein anderes Verhalten können Sie über ICLTAG_ MutualExclude als Maske übergeben.
Zur Laufzeit kann der Aktivierungszustand/Wählbarkeit über pOS_MenuItem->mi_ Flags ermittelt werden. Zum Ändern existieren die Funktionen pOS_SetWindowMenuChecker() und pOS_EnableWindowMenu(). Die dabei benötigte pOS_MenuNum setzen Sie wie folgt zusammen: Entweder tragen Sie den Wert als ULONG oder die drei Einzelkomponenten als UBYTEs ein. Die Punkte werden jeweils ab 0 gezählt und können theoretisch bis maximal 254 laufen. Die Zahl 255 (= 0xFF) hat eine Sonderstellung und bedeutet, daß dieser Eintrag nicht beachtet werden soll (wenn beispielsweise kein Sub-Menü existiert).

Die Menünummern für das zweite Menü, dritter Eintrag (kein Sub) würde sich somit aus

 
pOS_MenuNum->men_U.men_Pck[MENNUPCK_Title] = 1
pOS_MenuNum->men_U.men_Pck[MENNUPCK_Item]  = 2
pOS_MenuNum->men_U.men_Pck[MENNUPCK_Sub]   = 0xFF

oder aus

 
pOS_MenuNum->men_U.men_Num = 0x0102FF00

zusammensetzen. Über diese Menünummern können Sie auch die Adresse des Menüeintrags per pOS_GetMenuItemFromNum() ermitteln. Über pOS_CreateMenuTagA() erzeugen Sie aus der Menübeschreibung die interne Menüstruktur, die noch über pOS_PreLayoutMenu() vorbereitet werden muß. Danach kann sie bei Bedarf per pOS_SetMenuStrip() in das zugehörige Fenster eingehängt werden. Verwenden Sie mehrere Fenster, sollte das Menü in jedem Fenster verfügbar sein. Dabei dürfen Sie den einen Menüzeiger in mehreren Fenstern verwenden. Am Programmende ist die interne Menüstruktur mit pOS_DeleteMenu() freizugeben.

Nach der Menüauswahl sendet Intuition eine IDCMP_ MenuPick-Message. Dabei ist 9 im_Code-Feld die gewählte Menünummer vermerkt. Die Menüverteilung kann wieder mit einer verschachtelten Switch-Case-Konstellation ausgewertet werden. Bedenken Sie, daß der erste Eintrag immer 0 lautet. Alternativ können Sie statt der Zahlen auch auf Defines oder Enumerations zurückgreifen, um auch nach Menüumbauten keine großen Änderungen im Messagelloop durchführen zu müssen. Eine andere Möglichkeit ist, im User-Zeiger des Menüs eine Funktions-Adresse abzulegen, die man nur noch aufrufen muß.
Das p.OS-StyleGuide schreibt vor, daß Menüpunkt-Shortcuts, im Gegensatz zu Gadgets, nicht lokalisiert werden. Die gängigen Punkte und Shortcuts lauten somit:

Bei Auswahl eines »Jahreszeiten«-Menüeintrages wird die Wahl, wie bereits bei den Gadgets besprochen, über die zentrale Funktion SetDatas() für alle Gadgets aktiviert. Neu in dieser Funktion ist, daß die aktuelle Auswahl über pOS_ SetWindowMenuChecker auch im Menü gesetzt wird.
Bei der Auswahl von »Quit« wird, wie nicht anders zu erwarten, das Programm beendet. Dabei könnte z.B. noch eine Abfrage eingebaut werden, ob das Programm wirklich beendet oder ob evtl. veränderte Daten noch gespeichert werden sollen.

Project-Menü weitere Menüs Edit-Menü
NewNeu (N)Help Hilfe (?)CutAusschneiden (X)
OpenLaden (O)CopyKopieren (C)
SaveSpeichern (S)PasteEinfügen (V)
Save AsSpeichern unter (A)DeleteLöschen
PrintDrucken (P)UndoZurücknehmen (Z)
InfoInformationRedoWiederherstellen
QuitBeenden (Q)
Bei »Information« wird ein EasyRequester (per pOS_EasyRequestArgs()) angezeigt, der die üblichen Angaben, wie z.B. Programmname, Versionsnummer, Erstellungsdatum, Copyrightvermerk etc. enthält. Interessant ist dabei, daß der Requester vom Typ ESYTYP_Info ist. Damit teilen Sie p.OS die Art des Requesters mit. Der Benutzer kann einstellen, welche Grafik zu welchem Requestertyp angezeigt wird. So kann man bereits ohne Lesen des Textes eine Gruppeneinteilung vornehmen. Typenlosen Requestern können Sie mit ESYTYP_GfxFile auch eine beliebige Grafik zuordnen. Dabei ist der Type auf ESYTYP_Std zu setzen. Dieser Typ ohne GfxFile zeigt nur den Text an. Eine Liste der möglichen Typen und den Aufbau der zu übergebenden pOS_EasyRequester-Struktur finden Sie unter »p:pIntui/ EasyReq.h«. EasyRequester sind unter p.OS aber nicht nur einfache Rückfrage/Informations-Requester mit »Ok« oder »Weiter/Abbruch«, sie lassen sich vielmehr individuel programmieren. Man kann durchaus behaupten, daß EasyRequester vollwertige Fenster mit reduziertem Programmieraufwand erzeugen kann. Der Inhalt der Requester setzt sich nämlich wieder ­ wie sollte es anders sein ­ aus Objekten zusammen. Durch Ergänzen oder vollständiges Ersetzen der normalen Textbox/Gadgetgruppe können Sie z.B. eine Texteingabe anfordern. Die notwendigen Tags zur Beschreibung des Fensterinhalts (z.B. einer eigenen Gadgetgruppe) finden Sie unter »p:pIntui/Easy Tags.h«.

Die normalen Requester haben aber einen Nachteil ­ sie arbeiten synchron. Die Funktion »pOS_EasyRequestArgs()« kehrt erst dann zurück, wenn der Benutzer ein Gadget aus dem Requester anklickt, bzw. (optional) eine Nachricht entsprechend der gesetzten IDCMP-Flags aufgetreten ist. Während dieser Zeit kann Ihr Programm auch nicht auf Refresh-Anforderungen durch das OS reagieren. Hierbei zeigt sich aber der Vorteil, wenn das Fenster nur Gadgets enthält (z.B. auch für normale Textausgaben). Solche Gadgets können sich nämlich unabhängig vom Programm selbständig neu zeichnen und aufbauen.

Sollten Sie aber dennoch in die Situation kommen, daß während der Requesterausgabe das Programm weiterlaufen bzw. auf andere Nachrichten reagiert werden soll, existieren hierfür drei Funktionen. Mit »pOS_CreateRequestWin()« erzeugen Sie das Fenster identisch wie bei synchronen Requestern. Dabei erhalten Sie einen pOS_Window-Zeiger, den Sie zum Schließen des Requesters an »pOS_DeleteRequestWin()« übergeben müssen.

Einen normaler Messageloop könnte folgendermaßen aussehen:

 
ULONG sigs=pOS_WaitSignal(
	 (1L < window->win_UserPort->mp_SigBit) |
	 (1L < requester->win_UserPort->mp_SigBit) |
	 DOSSIGF_CTRL_C);

Eine häufige Anwendung könnte aber auch sein, daß der EasyRequester nur einen Abbruch-Schalter enthält, mit der sich die aktuelle Berechnung durch das Programm vorzeitig beenden läßt. Dann müssen Sie regelmäßig mit »pOS_RequestWinHandler()« den Zustand des Requesters abfragen. Die Funktion erzeugt folgende Returnwerte:

2: es liegt keine Nachricht vor (nur wenn Handler-Wait auf FALSE gesetzt wurde)
1: ein gesetztes IDCMP-Ereignis ist eingetreten
0: Schalter ganz rechts (meist Abbruch) wurde angeklickt (bzw. einziger vorhandener)
größer 0: Schalterposition fortlaufend von links ab 1 gezählt

Beachten Sie, daß das Schließen des Requesters nicht automatisch geschieht, sondern mit »pOS_DeleteRequestWin()« erledigt werden muß.
Wollen Sie den Requester mit einer Fortschrittsanzeige versehen, finden Sie das entsprechende Gadget »ObjATool_ProcessGadA.class« in der »ObjATool.library«.

Damit haben wir alle Komponenten für ein vollständiges GUI-Programm zusammen: Windows, Gadgets, Menüs sowie deren Erzeugung und Auswertung. Der letzte Schritt sollte das Lokalisieren des Programms sein. Dabei wird das Amiga-kompatible Catalog-Konzept verwendet. Eine Catalog-Datei öffnen Sie mit pOS_OpenCatalog() und schließen Sie am Programmende mit pOS_CloseCatalog(). Zur Laufzeit können Sie die Texte per pOS_GetCatalogStr() abfragen. Als letzten Parameter erwartet die Funktion einen Default-String, der zurückgegeben wird, wenn der Catalog nicht geöffnet werden konnte, bzw. wenn der String im Catalog fehlt. Vor oder nach Nutzung der Funktionen müssen Sie natürlich die »pLocale.library« öffnen und schließen.

Die erste Aktion des Programms sollte das Öffnen der pLocale.library und des Catalogs sein. Dadurch kann auch die Template-Erklärung landesspezifisch erfolgen. Für das Template sowie die im Programm fest eingebaute Sprache, empfehlen wir Englisch. Vor allem im Public Domain-Bereich kann durch die Catalog-Dateien eine große (sprachenunabhängige) Verbreitung erfolgen. Es findet sich immer jemand, der für eine fehlende Sprache die Cataloge ergänzt und somit das Programm noch mehr Benutzern zugänglich macht. Dabei sollte man aber auch immer die Anleitungsdatei mit übersetzen.

Auf die Lokalisierung eines Programms werden wir aber erst in lose folgenden, künftigen Kursteilen eingehen. Die Entwicklerunterlagen enthalten neben den Autodocs zu allen Libraries/Devices/Objekten auch jede Menge Beispielprogramme, die auf einzelne Funktionen eingehen. Bei weiteren Fragen können Sie sich direkt per E-Mail (»develop@ prodad.de«) an proDAD wenden. Mittlerweile existiert für p.OS auch eine Mailing-List, in der Sie als registrierter Entwickler Fragen und Erfahrungen weitergeben können.

bl


MagnaMedia Hauptseite

© Copyright by MagnaMedia Verlag AG, Haar bei München
Veröffentlichung und Vervielfältigung nur mit schriftlicher Genehmigung des Verlags


Kommentare, Fragen, Korrekturen und Kritik bitte an Webmaster AMIGA schicken.
Zuletzt aktualisiert am 06. August 1997.